package com.szeldon.pj.jps.visitor;

import java.util.logging.Logger;

import com.szeldon.pj.jps.data.store.SBAStore;
import com.szeldon.pj.jps.datastore.SBAObject;
import com.szeldon.pj.jps.datastore.SimpleObject;
import com.szeldon.pj.jps.envs.ENVS;
import com.szeldon.pj.jps.exp.AndBinaryExpression;
import com.szeldon.pj.jps.exp.AsBinaryExpression;
import com.szeldon.pj.jps.exp.BagNaryExpression;
import com.szeldon.pj.jps.exp.BiggerBinaryExpression;
import com.szeldon.pj.jps.exp.BinaryExpression;
import com.szeldon.pj.jps.exp.CountNaryExpression;
import com.szeldon.pj.jps.exp.DiffBinaryExpression;
import com.szeldon.pj.jps.exp.DivisionBinaryExpression;
import com.szeldon.pj.jps.exp.DotExpression;
import com.szeldon.pj.jps.exp.EqualsBinaryExpression;
import com.szeldon.pj.jps.exp.Expression;
import com.szeldon.pj.jps.exp.ForallBinaryExpression;
import com.szeldon.pj.jps.exp.GroupasBinaryExpression;
import com.szeldon.pj.jps.exp.IntegerTerminalExpression;
import com.szeldon.pj.jps.exp.MinusBinaryExpression;
import com.szeldon.pj.jps.exp.MultiplyBinaryExpression;
import com.szeldon.pj.jps.exp.NameExpression;
import com.szeldon.pj.jps.exp.NaryExpression;
import com.szeldon.pj.jps.exp.OrBinaryExpression;
import com.szeldon.pj.jps.exp.PlusBinaryExpression;
import com.szeldon.pj.jps.exp.RealTerminalExpression;
import com.szeldon.pj.jps.exp.StringTerminalExpression;
import com.szeldon.pj.jps.exp.TerminalExpression;
import com.szeldon.pj.jps.exp.UnionBinaryExpression;
import com.szeldon.pj.jps.exp.WhereExpression;
import com.szeldon.pj.jps.qres.AbstractQueryResult;
import com.szeldon.pj.jps.qres.IntegerResult;
import com.szeldon.pj.jps.qres.QResStack;
import com.szeldon.pj.jps.qres.RealResult;
import com.szeldon.pj.jps.qres.ReferenceResult;
import com.szeldon.pj.jps.qres.StringResult;

public class ASTVisitor implements Visitor {

	private static final Logger logger = Logger.getLogger(ASTVisitor.class.getName());
	private SBAStore store;
	private QResStack qResStack;
	private ENVS envs;

	/**
	 * 
	 * @param store
	 * @param qResStack
	 * @param envs
	 */
	public ASTVisitor(SBAStore store, QResStack qResStack, ENVS envs) {
		this.store = store;
		this.qResStack = qResStack;
		this.envs = envs;
	}

	@Override
	public void accept(Expression expression) {
		logger.info("accepting " + expression.getClass().getName());

		if (expression instanceof BinaryExpression) {
			handleBinaryExpression((BinaryExpression) expression);
		} else if (expression instanceof NaryExpression) {
			handleNaryExpression((NaryExpression) expression);
		} else if (expression instanceof NameExpression) {
			handleNameExpression((NameExpression) expression);
		} else if (expression instanceof TerminalExpression) {
			handleTerminalExpression((TerminalExpression) expression);
		} else {
			throw new IllegalStateException("Expression of " + expression.getClass().getName()
					+ " type isn't supported.");
		}

		logger.info("accepted " + expression.getClass().getName());
	}

	private void handleNaryExpression(NaryExpression expression) {

		if (expression instanceof BagNaryExpression) {
			handleBagExpression((BagNaryExpression) expression, expression.getExpressions());
		} else if (expression instanceof MinNaryExpression) {
			handleMinExpression((MinNaryExpression) expression, expression.getExpressions());
		} else if (expression instanceof CountNaryExpression) {
			handleCountExpression((CountNaryExpression) expression, expression.getExpressions());
		} else {
			throw new IllegalStateException("Expression of " + expression.getClass().getName()
					+ " type isn't supported.");
		}
	}

	private void handleCountExpression(CountNaryExpression expression, Expression[] expressions) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleMinExpression(MinNaryExpression expression, Expression[] expressions) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleNameExpression(NameExpression expression) {
		qResStack.push(envs.bind(expression.getName()));
	}

	private void handleBinaryExpression(BinaryExpression expression) {
		Expression leftExpression = expression.getLeftExpression();
		Expression rightExpression = expression.getRightExpression();
		if (expression instanceof DotExpression) {
			handleDotExpression((DotExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof WhereExpression) {
			handleWhereExpression((WhereExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof EqualsBinaryExpression) {
			handleEqualsExpression((EqualsBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof AsBinaryExpression) {
			handleAsExpression((AsBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof ForallBinaryExpression) {
			handleForallExpression((ForallBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof BiggerBinaryExpression) {
			handleBiggerExpression((BiggerBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof PlusBinaryExpression) {
			handlePlusExpression((PlusBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof MinusBinaryExpression) {
			handleMinusExpression((MinusBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof MultiplyBinaryExpression) {
			handleMultiplyExpression((MultiplyBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof DivisionBinaryExpression) {
			handleDivisionExpression((DivisionBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof AndBinaryExpression) {
			handleAndExpression((AndBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof OrBinaryExpression) {
			handleOrExpression((OrBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof DiffBinaryExpression) {
			handleOrExpression((DiffBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof UnionBinaryExpression) {
			handleUnionExpression((UnionBinaryExpression) expression, leftExpression, rightExpression);
		} else if (expression instanceof GroupasBinaryExpression) {
			handleGroupasExpression((GroupasBinaryExpression) expression, leftExpression, rightExpression);
		} else {
			throw new IllegalStateException("Expression of " + expression.getClass().getName()
					+ " type isn't supported.");
		}
	}

	private void handleGroupasExpression(GroupasBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleUnionExpression(UnionBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleOrExpression(DiffBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleOrExpression(OrBinaryExpression expression, Expression leftExpression, Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleAndExpression(AndBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleDivisionExpression(DivisionBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleMultiplyExpression(MultiplyBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleMinusExpression(MinusBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handlePlusExpression(PlusBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleBiggerExpression(BiggerBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleForallExpression(ForallBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {
		expression.accept(this, envs, qResStack, store);
	}

	private void handleBagExpression(BagNaryExpression expression, Expression[] expressions) {

		expression.accept(this, envs, qResStack, store);

	}

	private void handleAsExpression(AsBinaryExpression expression, Expression leftExpression, Expression rightExpression) {

		expression.accept(this, envs, qResStack, store);

	}

	private void handleEqualsExpression(EqualsBinaryExpression expression, Expression leftExpression,
			Expression rightExpression) {

		expression.accept(this, envs, qResStack, store);

	}

	private void handleWhereExpression(WhereExpression expression, Expression leftExpression, Expression rightExpression) {

		expression.accept(this, envs, qResStack, store);

	}

	private void handleDotExpression(DotExpression expression, Expression leftExpression, Expression rightExpression) {

		expression.accept(this, envs, qResStack, store);

	}

	private void handleTerminalExpression(TerminalExpression expression) {
		// bag version
		// AbstractQueryResult result = convertExpressionToResult(expression);
		// Set<AbstractQueryResult> resultsSet = new HashSet<AbstractQueryResult>();
		// resultsSet.add(result);
		// qResStack.push(new BagResult(resultsSet));

		// single version
		AbstractQueryResult result = convertExpressionToResult(expression);
		qResStack.push(result);
	}

	/**
	 * 
	 * @param result
	 * @param expression
	 * @return -1 - result is smaller than expression. 0 - result is equal to expression. 1 - result is larger than
	 *         expression.
	 */
	public static int compareAbstractQueryResultWithExpression(SBAStore store, AbstractQueryResult result,
			Expression expression) {
		int comparisonResult = 0;

		logger.info("comparing " + result + " with " + expression);

		if (result instanceof ReferenceResult) {
			comparisonResult = (-1) * compareReferenceWithExpression(store, (ReferenceResult) result, expression);
		} else {
			throw new UnsupportedOperationException("" + result.getClass().getName()
					+ " isn't currently supported for comparison.");
		}

		logger.info("comparison result=" + comparisonResult);

		return comparisonResult;
	}

	/**
	 * -1 if referenced object is smaller than result. 0 if they're equal. 1 if referenced object is bigger than result.
	 * 
	 * @param store
	 * @param result
	 * @param expression
	 * @return
	 */
	public static int compareReferenceWithExpression(SBAStore store, ReferenceResult result, Expression expression) {
		SBAObject objectForResult = store.retrieve(result.getReferencedObjectId());

		logger.info("comparing " + objectForResult + " with " + expression.getValueAsString());
		if (objectForResult instanceof SimpleObject) {
			return expression.compareTo((SimpleObject) objectForResult);
		} else {
			throw new IllegalStateException(objectForResult.getClass().getName()
					+ " isn't currently supported for comparison.");
		}
	}

	public static AbstractQueryResult convertExpressionToResult(Expression expression) {
		AbstractQueryResult result = null;

		if (expression instanceof TerminalExpression) {
			if (expression instanceof IntegerTerminalExpression) {
				result = new IntegerResult(((IntegerTerminalExpression) expression).getValue());
			} else if (expression instanceof StringTerminalExpression) {
				result = new StringResult(((StringTerminalExpression) expression).getValue());
			} else if (expression instanceof RealTerminalExpression) {
				result = new RealResult(((RealTerminalExpression) expression).getValue());
			} else {
				throw new IllegalStateException("Expression of " + expression.getClass().getName()
						+ " type can't be converted between expression and AbstractQueryResult at the moment.");
			}
		} else {
			throw new IllegalStateException("Expression of " + expression.getClass().getName()
					+ " type can't be converted between expression and AbstractQueryResult at the moment.");
		}

		return result;
	}

	/**
	 * -1 - first expression is smaller than second one. 0 - expressions are equal. 1 - first expression is larger than
	 * second one.
	 * 
	 * @param store
	 * @param firstExpression
	 * @param secondExpression
	 * @return
	 */
	public static int compareExpressions(SBAStore store, TerminalExpression firstExpression,
			TerminalExpression secondExpression) {
		return firstExpression.compareTo(secondExpression);
	}

	/**
	 * -1 - first result is smaller than second one. 0 - results are equal. 1 - first result is larger than second one.
	 * 
	 * 
	 * @param store
	 * @param firstResult
	 * @param secondResult
	 * @return
	 */
	public static int compareResults(SBAStore store, AbstractQueryResult firstResult, AbstractQueryResult secondResult) {
		return firstResult.compareTo(secondResult);
	}
}